#include "winghexdisasm.h"
#include <QApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QMetaEnum>
#include <QSettings>
#include <QVBoxLayout>

Q_DECLARE_METATYPE(cs_arch)
Q_DECLARE_METATYPE(cs_mode)

#define ICONRES(name) QIcon(":/WingHexDisasm/img/" name ".png")
#define HOSTICONRES(name) QIcon(HOSTRESPIMG(name))

WingHexDisasm::WingHexDisasm(QObject *parent) { Q_UNUSED(parent); }

bool WingHexDisasm::init(QList<WingPluginInfo> loadedplugin) {
  Q_UNUSED(loadedplugin);

  if (SDKVERSION < 8) {
    QMessageBox::critical(nullptr, "Error",
                          "UnSupported Plugin System Version!",
                          QMessageBox::Ok);
    return false;
  }

  auto translator = new QTranslator(this);

  auto s = GETPLUGINQM("WingHexDisasm.qm");
  if (!translator->load(s) || !QApplication::installTranslator(translator)) {
    QMessageBox::critical(nullptr, "Error", "Error Loading Translation File!",
                          QMessageBox::Ok);
    return false;
  }

  w = new QWidget;
  auto vlayout = new QVBoxLayout(w);
  auto hlayout = new QHBoxLayout;

  hlayout->addWidget(new QLabel(tr("Arch :"), w));
  hlayout->addSpacing(10);

  // 默认 x86_64 64 位指令
  arch = new QComboBox(w);
  QStringList archs({"ARM", "ARM64", "MIPS", "X86_64", "PPC", "SPARC", "SYSZ",
                     "XCORE", "M68K", "TMS320C64X", "M680X", "EVM", "MOS65XX",
                     "WASM", "BPF", "RISCV"});
  arch->addItems(archs);
  arch->setCurrentIndex(3);

  void (QComboBox::*indexChanged)(int index) = &QComboBox::currentIndexChanged;
  connect(arch, indexChanged, [=](int index) {
    switch (index) {
    case 0:
      carch = cs_arch::CS_ARCH_ARM;
      break;
    case 1:
      carch = cs_arch::CS_ARCH_ARM64;
      break;
    case 2:
      carch = cs_arch::CS_ARCH_MIPS;
      break;
    case 3:
      carch = cs_arch::CS_ARCH_X86;
      break;
    case 4:
      carch = cs_arch::CS_ARCH_PPC;
      break;
    case 5:
      carch = cs_arch::CS_ARCH_SPARC;
      break;
    case 6:
      carch = cs_arch::CS_ARCH_SYSZ;
      break;
    case 7:
      carch = cs_arch::CS_ARCH_XCORE;
      break;
    case 8:
      carch = cs_arch::CS_ARCH_M68K;
      break;
    case 9:
      carch = cs_arch::CS_ARCH_TMS320C64X;
      break;
    case 10:
      carch = cs_arch::CS_ARCH_M680X;
      break;
    case 11:
      carch = cs_arch::CS_ARCH_EVM;
      break;
    case 12:
      carch = cs_arch::CS_ARCH_MOS65XX;
      break;
    case 13:
      carch = cs_arch::CS_ARCH_WASM;
      break;
    case 14:
      carch = cs_arch::CS_ARCH_BPF;
      break;
    case 15:
      carch = cs_arch::CS_ARCH_RISCV;
      break;
    }
  });

  hlayout->addWidget(arch);
  hlayout->addSpacing(30);

  hlayout->addWidget(new QLabel(tr("Mode :"), w));
  hlayout->addSpacing(10);

  mode = new QComboBox(w);
  QStringList modes({"x16", "x32", "x64", "THUMB", "MIPS32", "MIPS64"});
  mode->addItems(modes);
  mode->setCurrentIndex(2);

  connect(mode, indexChanged, [=](int index) {
    switch (index) {
    case 0:
      cmode = cs_mode::CS_MODE_16;
      break;
    case 1:
      cmode = cs_mode::CS_MODE_32;
      break;
    case 2:
      cmode = cs_mode::CS_MODE_64;
      break;
    case 3:
      cmode = cs_mode::CS_MODE_THUMB;
      break;
    case 4:
      cmode = cs_mode::CS_MODE_MIPS32;
      break;
    case 5:
      cmode = cs_mode::CS_MODE_MIPS64;
      break;
    }
  });

  hlayout->addWidget(mode);
  hlayout->addSpacing(10);

  cbintel = new QCheckBox("Intel", w);
  cbintel->setChecked(true);
  connect(cbintel, &QCheckBox::toggled, [=] { this->disasm(tmpbuf); });
  hlayout->addWidget(cbintel);
  vlayout->addSpacing(10);

  txtAsm = new QTextBrowser(w);
  txtAsm->setUndoRedoEnabled(false);
  vlayout->addItem(hlayout);
  vlayout->addWidget(txtAsm);

  PluginDockWidgetInit(dw, w, tr("DisasmWindow"), "DisasmWindow");

  PluginMenuInitBegin(menu, tr("WingHexDisasm")) {
    menu->setIcon(ICONRES("icon"));
    PluginMenuAddItemIconAction(menu, tr("Disasm"), ICONRES("analyse"),
                                WingHexDisasm::on_disasm);
    PluginMenuAddItemIconAction(menu, tr("Clear"), HOSTICONRES("clearhis"),
                                WingHexDisasm::on_clear);
    menu->addSeparator();
    PluginMenuAddItemIconLamba(menu, tr("Author"), HOSTICONRES("author"), [=] {
      auto authord =
          newAboutDialog(QPixmap(), {":/WingHexDisasm", ":/WingHexDisasm/img"});
      authord->exec();
      delete authord;
    });
    PluginMenuAddItemIconLamba(menu, tr("Sponsor"), HOSTICONRES("sponsor"),
                               [=] {
                                 auto sponsor = newSponsorDialog();
                                 sponsor->exec();
                                 delete sponsor;
                               });
  }
  PluginMenuInitEnd();

  QMenu *tmenu;
  PluginMenuInitBegin(tmenu, "") {
    PluginMenuAddItemIconAction(tmenu, tr("Disasm"), ICONRES("analyse"),
                                WingHexDisasm::on_disasm);
    PluginMenuAddItemIconAction(tmenu, tr("Clear"), HOSTICONRES("clearhis"),
                                WingHexDisasm::on_clear);
  }
  PluginMenuInitEnd();
  PluginToolButtonInit(tbtn, tmenu, ICONRES("icon"));
  loadSetting();
  return true;
}

WingHexDisasm::~WingHexDisasm() {}

void WingHexDisasm::unload() { saveSetting(); }

int WingHexDisasm::sdkVersion() { return SDKVERSION; }

QMenu *WingHexDisasm::registerMenu() { return menu; }

QToolButton *WingHexDisasm::registerToolButton() { return tbtn; }

void WingHexDisasm::registerDockWidget(
    QHash<QDockWidget *, Qt::DockWidgetArea> &rdw) {
  rdw.insert(dw, Qt::DockWidgetArea::NoDockWidgetArea);
}

const QString WingHexDisasm::pluginName() { return tr("WingHexDisasm"); }

const QString WingHexDisasm::pluginAuthor() { return WINGSUMMER; }

uint WingHexDisasm::pluginVersion() { return 1; }

const QString WingHexDisasm::signature() { return WINGSUMMER; }

const QString WingHexDisasm::pluginComment() {
  return tr("A small disassembly plugin for WingHexExplorer.");
}

void WingHexDisasm::plugin2MessagePipe(WingPluginMessage type,
                                       QList<QVariant> msg) {
  Q_UNUSED(type);
  Q_UNUSED(msg);
}

void WingHexDisasm::disasm(QByteArray code) {
  csh handle;
  cs_err err = cs_open(carch, cmode, &handle);

  if (err != CS_ERR_OK) {
    txtAsm->setText(tr("Error: Failed capstone initialization"));
  } else if (code.length()) {
    size_t count;
    cs_insn *insn;
    cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
    cs_option(handle, CS_OPT_SKIPDATA, CS_OPT_ON);

    if (cbintel->isChecked()) {
      cs_option(handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL);
    } else {
      cs_option(handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT);
    }

    auto *t = code.data();
    count = cs_disasm(handle, reinterpret_cast<uint8_t *>(t),
                      size_t(code.size()), 0, 0, &insn);
    if (count > 0) {
      txtAsm->clear();
      size_t j;
      QStringList asms;
      for (j = 0; j < count; j++) {
        asms << (QString("%1 %2;").arg(insn[j].mnemonic).arg(insn[j].op_str));
      }
      txtAsm->setText(asms.join('\n'));
      cs_free(insn, count);
    } else {
      txtAsm->setText(tr("Error: Failed to disassemble given op codes"));
    }
    cs_close(&handle);
    tmpbuf.swap(code);
  }
}

void WingHexDisasm::on_disasm() {
  if (reader.currentDoc() < 0) {
    this->toast(ICONRES("icon"), tr("No Document Opened"));
    return;
  }
  if (reader.selectLength()) {
    this->disasm(reader.selectedBytes());
  } else {
    this->toast(ICONRES("icon"), tr("No Selection Bytes"));
  }
}

void WingHexDisasm::on_clear() { txtAsm->clear(); }

void WingHexDisasm::saveSetting() {
  QSettings settings("wingsummer", "WingHexDisasm");
  settings.setValue("arch", carch);
  settings.setValue("mode", cmode);
}

void WingHexDisasm::loadSetting() {
  QSettings settings("wingsummer", "WingHexDisasm");
  carch = settings.value("arch", cs_arch::CS_ARCH_X86).value<cs_arch>();
  cmode = settings.value("mode", cs_mode::CS_MODE_64).value<cs_mode>();
}

#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(WingHexDisasm, GenericPlugin)
#endif // QT_VERSION < 0x050000
